#pragma once

#include "../RTDCmptTraits.h"

namespace Ubpa::UECS {
	inline RTDCmptTraits& RTDCmptTraits::RegisterSize(TypeID type, std::size_t size) {
		sizeofs.emplace(type, size);
		return *this;
	}

	inline RTDCmptTraits& RTDCmptTraits::RegisterAlignment(TypeID type, std::size_t alignment) {
		alignments.emplace(type, alignment);
		return *this;
	}

	inline RTDCmptTraits& RTDCmptTraits::RegisterDefaultConstructor(TypeID type, std::function<void(void*)> f) {
		default_constructors.emplace(type, std::move(f));
		return *this;
	}

	inline RTDCmptTraits& RTDCmptTraits::RegisterCopyConstructor(TypeID type, std::function<void(void*,void*)> f) {
		copy_constructors.emplace(type, std::move(f));
		return *this;
	}

	inline RTDCmptTraits& RTDCmptTraits::RegisterMoveConstructor(TypeID type, std::function<void(void*,void*)> f) {
		move_constructors.emplace(type, std::move(f));
		return *this;
	}

	inline RTDCmptTraits& RTDCmptTraits::RegisterMoveAssignment(TypeID type, std::function<void(void*,void*)> f) {
		move_assignments.emplace(type, std::move(f));
		return *this;
	}

	inline RTDCmptTraits& RTDCmptTraits::RegisterDestructor(TypeID type, std::function<void(void*)> f) {
		destructors.emplace(type, std::move(f));
		return *this;
	}

	inline RTDCmptTraits& RTDCmptTraits::RegisterName(TypeID type, std::string name) {
		names.emplace(type, std::move(name));
		return *this;
	}

	inline std::size_t RTDCmptTraits::Sizeof(TypeID type) const {
		auto target = sizeofs.find(type);
		assert(target != sizeofs.end());
		return target->second;
	}

	inline std::size_t RTDCmptTraits::Alignof(TypeID type) const {
		auto target = alignments.find(type);

		return target != alignments.end() ? target->second : DefaultAlignment();
	}

	inline void RTDCmptTraits::DefaultConstruct(TypeID type, void* cmpt) const {
		auto target = default_constructors.find(type);

		if (target != default_constructors.end())
			target->second(cmpt);
	}

	inline void RTDCmptTraits::CopyConstruct(TypeID type, void* dst, void* src) const {
		auto target = copy_constructors.find(type);

		if (target != copy_constructors.end())
			target->second(dst, src);
		else
			memcpy(dst, src, Sizeof(type));
	}

	inline void RTDCmptTraits::MoveConstruct(TypeID type, void* dst, void* src) const {
		auto target = move_constructors.find(type);

		if (target != move_constructors.end())
			target->second(dst, src);
		else
			memcpy(dst, src, Sizeof(type));
	}

	inline void RTDCmptTraits::MoveAssign(TypeID type, void* dst, void* src) const {
		auto target = move_assignments.find(type);

		if (target != move_assignments.end())
			target->second(dst, src);
		else
			memcpy(dst, src, Sizeof(type));
	}

	inline void RTDCmptTraits::Destruct(TypeID type, void* cmpt) const {
		auto target = destructors.find(type);
		if (target != destructors.end())
			target->second(cmpt);
	}

	inline std::string_view RTDCmptTraits::Nameof(TypeID type) const {
		auto target = names.find(type);
		if (target != names.end())
			return target->second;
		else
			return {};
	}

	template<typename... Cmpts>
	void RTDCmptTraits::Register() {
		(RegisterOne<Cmpts>(), ...);
	}

	template<typename Cmpt>
	void RTDCmptTraits::RegisterOne() {
		static_assert(!IsTaggedCmpt_v<Cmpt>, "<Cmpt> should not be tagged");
		static_assert(std::is_default_constructible_v<Cmpt>, "<Cmpt> must be default-constructible");
		static_assert(std::is_copy_constructible_v<Cmpt> || std::is_constructible_v<Cmpt, Cmpt&>,
			"<Cmpt> must be copy-constructible or constructible with <Cmpt&>");
		static_assert(std::is_move_constructible_v<Cmpt>, "<Cmpt> must be move-constructible");
		static_assert(std::is_move_assignable_v<Cmpt>, "<Cmpt> must be move-assignable");
		static_assert(std::is_destructible_v<Cmpt>, "<Cmpt> must be destructible");

		constexpr TypeID type = TypeID_of<Cmpt>;

		sizeofs.emplace(type, sizeof(Cmpt));
		alignments.emplace(type, alignof(Cmpt));
		names.emplace(type, std::string{ type_name<Cmpt>().View() });

		if constexpr (!std::is_trivially_default_constructible_v<Cmpt>) {
			default_constructors.emplace(type, [](void* cmpt) {
				new(cmpt)Cmpt;
			});
		}
		if constexpr (!std::is_trivially_destructible_v<Cmpt>) {
			destructors.emplace(type, [](void* cmpt) {
				static_cast<Cmpt*>(cmpt)->~Cmpt();
			});
		}
		if constexpr (!std::is_trivially_move_constructible_v<Cmpt>) {
			move_constructors.emplace(type, [](void* dst, void* src) {
				new(dst)Cmpt(std::move(*static_cast<Cmpt*>(src)));
			});
		}
		if constexpr (!std::is_trivially_move_assignable_v<Cmpt>) {
			move_assignments.emplace(type, [](void* dst, void* src) {
				*static_cast<Cmpt*>(dst) = std::move(*static_cast<Cmpt*>(src));
			});
		}
		if constexpr (!std::is_trivially_copy_constructible_v<Cmpt>) {
			copy_constructors.emplace(type, [](void* dst, void* src) {
				new(dst)Cmpt(*static_cast<Cmpt*>(src));
			});
		}
	}

	template<typename Cmpt>
	void RTDCmptTraits::Deregister() {
		constexpr TypeID type = TypeID_of<Cmpt>;

		sizeofs.erase(type);
		alignments.erase(type);
		names.erase(type);

		if constexpr (!std::is_trivially_constructible_v<Cmpt>)
			default_constructors.erase(type);
		if constexpr (!std::is_trivially_destructible_v<Cmpt>)
			destructors.erase(type);
		if constexpr (!std::is_trivially_move_constructible_v<Cmpt>)
			move_constructors.erase(type);
		if constexpr (!std::is_trivially_move_assignable_v<Cmpt>)
			move_assignments.erase(type);
		if constexpr (!std::is_trivially_copy_constructible_v<Cmpt>)
			copy_constructors.erase(type);
	}

	inline RTDCmptTraits& RTDCmptTraits::Deregister(TypeID type) noexcept {
		names.erase(type);
		sizeofs.erase(type);
		alignments.erase(type);
		default_constructors.erase(type);
		copy_constructors.erase(type);
		move_constructors.erase(type);
		move_assignments.erase(type);
		destructors.erase(type);
		return *this;
	}

	inline RTDCmptTraits& RTDCmptTraits::Clear() {
		names.clear();
		sizeofs.clear();
		alignments.clear();
		default_constructors.clear();
		copy_constructors.clear();
		move_constructors.clear();
		move_assignments.clear();
		destructors.clear();
		return *this;
	}
}
